﻿\subsection[开启 32 位保护模式]{开启 32 位保护模式 \protect\footnote{若不作特别说明，文中提及的保护模式均指 32 位保护模式。}}

为了实现对内存的访问控制，x86 体系结构引入了保护模式的概念。
保护模式中仍然沿用了实模式下内存分段的概念，在保证向下兼容的同时增加了对内存段的访问保护。
在保护模式下，对内存的访问仍通过指定内存段、给定段内偏移的方式进行，每个内存段被赋予诸如段的地址范围、特权级等属性，
内存段这些属性存放在一个名为全局描述符（Global Descriptor）的数据结构中。
若干个全局描述符连续存放在某一块内存区域，形成了全局描述符表（Global Descriptor Table）。
与全局描述符和全局描述符表相对的是局部描述符（Local Descriptor）和局部描述符表（Local Descriptor Table），局部描述符用于支持对某个程序的分段。
现代操作系统多采用平坦内存模型，在 x86 平台上，一般将 GDT 中的描述符范围设置成处理器最大寻址空间来实现基本平坦模型。

GDT 的地址及大小信息保存在专门的 \lstinline{GDTR} 寄存器中（图 \ref{GDTR}）。
x86 提供了专门的指令 \lstinline{lgdt}，用于设置 \lstinline{GDTR} 寄存器。
\lstinline{GDTR} 寄存器保存了 GDT 的基址，由 \lstinline{Limit} 字段指出 GDT 中允许的最大偏移量。
其中，GDT 的 \lstinline{0} 号表项用作错误检查，设置 GDT 时不应使用，任何尝试访问 0 号段的指令都会触发一般保护异常。

\begin{figure}[h!]
    \Centering
    \caption{GDTR 寄存器}
    \label{GDTR}
    \includegraphics[scale=0.8]{build/Paper/Assets/GDTR.pdf}
\end{figure}

保护模式下的段寄存器中存放的是 \textbf{段选择子（Segment Selector，常简称为选择子）}。
选择子中存放了描述符的编号、类型（全局描述符或局部描述符）、请求特权级（图 \ref{SEGMENT_SELECTOR}）。
当需要访问某个内存地址时，首先要指定段选择子。
当前指令所在的段选择子保存在 \lstinline{CS} 寄存器中，指令从内存中获取数据时时，默认的数据段选择子保存在 \lstinline{DS} 寄存器中。

\begin{figure}[h!]
    \Centering
    \caption{段选择子结构}
    \label{SEGMENT_SELECTOR}
    \includegraphics[scale=0.8]{build/Paper/Assets/Segment-Selector.pdf}
\end{figure}

x86 体系结构下的任何访存操作都必须通过分段机制，这意味着访问具有不同功能的段需要频繁地切换寄存器中的段选择子，这会给程序编写和内存管理带来很大麻烦。
另外，x86 保护模式还提供了分页机制，分页机制同样也提供了对内存访问的保护功能，这就造成了分段机制和分页机制在功能上的重复。
综合考虑以上因素，现代操作系统设计中一般采用平坦模型。平坦内存模型中，所有的指令和数据都处于同一个段内。
就 x86 体系结构而言，要实现最简平坦模型只需要在 GDT 设置两个表项：
一个表项指示指令段，另一个表项指示数据段，段的范围覆盖整个 32 位的地址空间。
之所以要区分指令段和数据段，是因为 x86 内存段必须区分指令段和数据段两种类型。
在现代操作系统中，\lstinline{CS} 寄存器存放代码段的选择子，\lstinline{DS} 寄存器存放数据段的选择子，其余的段寄存器（\lstinline{ES}、\lstinline{FS}、\lstinline{GS}）原则上不使用。
有效的段选择子索引只能从 1 开始，平坦模型下不使用的段寄存器，可使其选中 0 号段。

本项目中，初始的 GDT 包含以下几个段描述符：

\begin{itemize}
  \item 空段：用作错误检测。
  \item 内核指令段：内核程序的指令，覆盖整个虚拟空间。
  \item 内核数据段：内核程序的数据，覆盖整个虚拟空间。
% \item 显示适配器（彩色模式）
% \item 显存适配器（黑白模式）
  \item 显示适配器（文字模式）：显存，只允许由内核控制。
\end{itemize}

其中，指令段和数据段覆盖整个 4 GiB 虚拟内存空间（尽管此时尚未开启分页），显存段中保存显存段的物理地址范围。% 假定 GDT 起始地址为 \lstinline{GDT_BASE}，最大偏移量为 \lstinline{GDT_LIMIT}，就可以通过下述指令加载 GDT。需要注意的是此时 CPU 尚处于实模式，不同于保护模式下所有寄存器都可用于基址、变址寻址，实模式下仅 \lstinline{BX} 和 \lstinline{BP} 寄存器可用于基址寻址，仅 \lstinline{SI} 和 \lstinline{DI} 寄存器可用于变址寻址。因此需要将栈顶指针 \lstinline{SP} 的值暂存入 \lstinline{BP} 后，使用 \lstinline{BP} 进行寻址。
% \begin{minted}{nasm}
% bits 16 ; 16 位指令
%     push bp
%     push dword GDT_BASE
%     push dword GDT_LIMIT
%     mov bp, sp
%     lgdt [bp]
%     cli
% \end{minted}

进入保护模式前，还需要执行关中断指令 \lstinline{cli}，以防止中断标志位被意外打开。
BIOS 为实模式下的 CPU 设置了中断向量表，因此实模式下的中断可被正确地处理。
不同主板 BIOS 的实现（或模拟器、虚拟机平台提供的 BIOS）有所差异，很有可能在实模式下是允许 CPU 接受并处理外部中断事件（如键盘输入就是一类中断事件）的。
进入保护模式后，如不将中断标志位 \lstinline{EFLAGS.IF} 清零，将导致 CPU 在开中断的情况下进入位保护模式。
此后一旦触发外部中断（如时钟中断）事件且 CPU 受理，CPU 会依据中断描述符寄存器 \lstinline{IDTR} 试图访问中断描述符表（Interupt Descritptor Table，IDT），并根据 IDT 中指示的段执行对应地址处的中断例程（实际并不存在），其后果无法预料。
有关中断和异常处理的内容将在后续小节详细展开。
。
% \begin{minted}[breaklines, breakautoindent=true]{nasm}
% bits 16
% Boot.Loader.ProtectedEnable:
%     mov eax, cr0
%     or eax, 0b0000_0001
%     mov cr0, eax
% \end{minted}
正确加载 GDT 后，打开 \lstinline{CR0.PE}（Protected Enable），即可使能保护模式
打开 \lstinline{CR0.PE} 位后，CPU 指令流水线上的下几条指令仍然是按照 16 位指令的方式进行译码的，而保护模式开启后执行的指令都必须按照 32 位指令的方式进行译码。
因此，使能 \lstinline{CR0.PE} 后紧跟的指令必须是 16 位的、能够清空当前流水线的指令——远跳转指令。
16 位与 32 位远跳转指令的指令码相同，且即便跳转的目标位置就是下一条指令所在的位置，CPU 仍会清空当前的流水线。
通过这种方式人为地触发流水线断流，就能够保证后续的 32 位指令都能够被正确译码。
此外，远跳转指令还有一个功效，就是将代码段寄存器 \lstinline{CS} 设置成远跳转指令立即数中的代码段选择子（下述代码中的 \lstinline{SEGMENT_SELECTOR_CODE}）。

\begin{minted}[breaklines, breakautoindent=true]{nasm}
bits 16
Boot.Loader.RefreshPipeline:
    jmp dword SEGMENT_SELECTOR_CODE:Boot.Loader.EnterProtectedMode
Boot.Loader.EnterProtectedMode
\end{minted}

进入保护模式后，需要执行对段寄存器的初始化操作，其中最关键的就是将数据段寄存器 \lstinline{DS} 和堆栈段寄存器 \lstinline{SS} 设置成数据段选择子。
在操作系统调入前，\lstinline{ES}（\lstinline{insw} 指令需要使用）、\lstinline{FS} 均指向数据段选择子，\lstinline{GS} 指向显存文本模式段，用于引导加载器字符串打印函数。
进入内核后，由于采用了平坦内存模型，段寄存器将很少被使用到。
因此，在操作系统内核程序装载完成后，需将除 \lstinline{CS}、\lstinline{DS}、\lstinline{SS} 以外的段寄存器全部清零。

% \begin{minted}[breaklines, breakautoindent=true]{nasm}
% bits 32
% Boot.Loader.EnterProtectedMode:
%     mov ax, SEGMENT_SELECTOR_DATA
%     mov ds, ax
%     mov es, ax
%     mov fs, ax
%     mov ss, ax
%     mov ax, SEGMENT_SELECTOR_VIDEO_TEXT_MODE
%     mov gs, ax
%     mov esp, LOADER_STACK_BASE ; LOADER_STACK_BASE 以下为专供引导加载器使用的栈空间
% \end{minted}
